#!/usr/bin/env bash

set -o errexit
set -o pipefail

DC="${DC:-exec}"

# Disable tty allocation for Docker Compose when none is available, such as
# in CI or when you pipe something into the run script. 0 = stdin, 1 = stdout.
TTY="${TTY:-}"
if ! [ -t 0 ] || ! [ -t 1 ]; then
  TTY="-T"
fi

# -----------------------------------------------------------------------------
# Helper functions start with _ and aren't listed in this script's help menu.
# -----------------------------------------------------------------------------

_dc() {
  # shellcheck disable=SC2086
  docker compose "${DC}" ${TTY} "${@}"
}

_dc_run() {
  DC="run" _dc --no-deps --rm "${@}"
}

# -----------------------------------------------------------------------------

cmd() {
  # Run any command you want in the web container
  _dc web "${@}"
}

manage() {
  # Run any manage.py commands

  # We need to collectstatic before we run our tests.
  if [ "${1-''}" == "test" ]; then
    cmd python3 manage.py collectstatic --no-input
  fi

  cmd python3 manage.py "${@}"
}

lint:dockerfile() {
  # Lint Dockerfile
  docker container run --rm -i \
    -v "${PWD}/.hadolint.yaml:/.config/hadolint.yaml" \
    hadolint/hadolint hadolint "${@}" - <Dockerfile
}

lint:shell() {
  # Lint shell scripts
  local cmd=(shellcheck)

  if ! command -v shellcheck >/dev/null 2>&1; then
    local cmd=(docker container run --rm -i -v "${PWD}:/mnt" koalaman/shellcheck:stable)
  fi

  find . -type f \
    ! -path "./.git/*" \
    ! -path "./.ruff_cache/*" \
    ! -path "./.pytest_cache/*" \
    ! -path "./assets/*" \
    ! -path "./public/*" \
    ! -path "./public_collected/*" \
    -exec grep --quiet '^#!.*sh' {} \; -exec "${cmd[@]}" {} +
}

lint() {
  # Lint Python code
  cmd ruff check "${@}"
}

format:shell() {
  # Format shell scripts
  local cmd=(shfmt)

  if ! command -v shfmt >/dev/null 2>&1; then
    local cmd=(docker container run --rm -i -v "${PWD}:/mnt" -u "$(id -u):$(id -g)" -w /mnt mvdan/shfmt:v3)
  fi

  local maybe_write=("--write")

  for arg in "${@}"; do
    if [ "${arg}" == "-d" ] || [ "${arg}" == "--diff" ]; then
      unset "maybe_write[0]"
    fi
  done

  "${cmd[@]}" "${maybe_write[@]}" "${@}" .
}

format() {
  # Format Python code
  cmd ruff check --fix
  cmd ruff format "${@}"
}

quality() {
  # Perform all code quality commands together
  lint:dockerfile
  lint:shell
  lint

  format:shell
  format
}

secret() {
  # Generate a random secret that can be used for your SECRET_KEY and more
  cmd python3 -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"
}

shell() {
  # Start a shell session in the web container
  cmd bash "${@}"
}

psql() {
  # Connect to PostgreSQL
  # shellcheck disable=SC1091
  . .env
  _dc postgres psql -U "${POSTGRES_USER}" "${@}"
}

redis-cli() {
  # Connect to Redis
  _dc redis redis-cli "${@}"
}

deps:install() {
  # Install back-end and / or front-end dependencies
  local no_build="${1:-}"

  [ -z "${no_build}" ] && docker compose down && docker compose build

  _dc_run js yarn install
  _dc_run web bash -c "cd .. && bin/uv-install"
}

uv() {
  # Run any uv commands
  cmd uv "${@}"
}

uv:outdated() {
  # List any installed packages that are outdated
  _dc_run web uv tree --outdated --depth 1 "${@}"
}

yarn() {
  # Run any yarn commands
  _dc js yarn "${@}"
}

yarn:outdated() {
  # List any installed packages that are outdated
  _dc_run js yarn outdated
}

yarn:build:js() {
  # Build JS assets, this is meant to be run from within the assets container
  mkdir -p ../public/js
  node esbuild.config.mjs
}

yarn:build:css() {
  # Build CSS assets, this is meant to be run from within the assets container
  local args=()

  if [ "${NODE_ENV:-}" == "production" ]; then
    args=(--minify)
  else
    args=(--watch)
  fi

  mkdir -p ../public/css
  DEBUG=0 tailwindcss -i css/app.css -o ../public/css/app.css "${args[@]}"
}

clean() {
  # Remove cache and other machine generates files
  rm -rf public/*.* public/admin public/js public/css public/images public/fonts \
    public_collected/*.* public_collected/admin public_collected/js \
    public_collected/css public_collected/images public_collected/fonts \
    .ruff_cache/ .pytest_cache/ .coverage celerybeat-schedule

  touch public/.keep public_collected/.keep
}

ci:install-deps() {
  # Install Continuous Integration (CI) dependencies
  sudo apt-get install -y curl
  sudo curl \
    -L https://raw.githubusercontent.com/nickjj/wait-until/v0.2.0/wait-until \
    -o /usr/local/bin/wait-until && sudo chmod +x /usr/local/bin/wait-until
}

ci:test() {
  # Execute Continuous Integration (CI) pipeline
  lint:dockerfile "${@}"
  lint:shell
  format:shell --diff

  cp --no-clobber .env.example .env

  docker compose build
  docker compose up -d

  # shellcheck disable=SC1091
  . .env
  wait-until "docker compose exec -T \
    -e PGPASSWORD=${POSTGRES_PASSWORD} postgres \
    psql -U ${POSTGRES_USER} ${POSTGRES_USER} -c 'SELECT 1'"

  docker compose logs

  lint "${@}"
  format --check --diff
  manage migrate
  manage test
}

help() {
  printf "%s <task> [args]\n\nTasks:\n" "${0}"

  compgen -A function | grep -v "^_" | cat -n

  printf "\nExtended help:\n  Each task has comments for general usage\n"
}

# This idea is heavily inspired by: https://github.com/adriancooney/Taskfile
TIMEFORMAT=$'\nTask completed in %3lR'
time "${@:-help}"
